package dd.lo.practice;

import dd.lo.util.ImageUtils;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.Arrays;

public class HistCalcDisplay extends Application {

    public static void start(String[] args) {
        launch(args);
    }

    private static final int DEFAULT_WIDTH = 1200;
    private static final int DEFAULT_HEIGHT = 900;

    private static final int SHOW_IMG_WIDTH = 400;
    private static final int SHOW_IMG_HEIGHT = 300;

    private Mat srcImg;

    private ImageView srcImage;
    private ImageView hsHist;
    private ImageView simpleHist;
    private ImageView backProjHist;

    private void imageProcess() {
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() {
                if (null == srcImg || srcImg.empty()) return null;
                //显示原图
                Platform.runLater(() -> showImage(new Mat(srcImg, new Rect(0, 0, srcImg.width(), srcImg.height())), srcImage, "原图"));

                MatOfInt histSize;
                MatOfFloat ranges;
                MatOfInt channels;
                Core.MinMaxLocResult minMaxLocResult;
                double maxVal;
                int scale;

                //------计算H-S直方图
                Mat hsImg = null;
                if (srcImg.channels() > 1) {
                    hsImg = new Mat();
                    Core.copyTo(srcImg, hsImg, new Mat());
                    if (srcImg.channels() > 1) {
                        Imgproc.cvtColor(hsImg, hsImg, Imgproc.COLOR_BGR2HSV);
                    }
                    //将色调量化为30个等级（横坐标），将饱和度量化为32个等级（纵坐标）
                    int hueBinNum = 30, saturationBinNum = 32;
                    histSize = new MatOfInt(hueBinNum, saturationBinNum);
                    //定义色调的变化范围为0~179，定义饱和度变化范围为0~255
                    ranges = new MatOfFloat(0, 180, 0, 256);
                    //指定calHist将计算第0通道和第1通道的直方图
                    channels = new MatOfInt(0, 1);
                    //H-S直方图计算结果
                    Mat hsHistResult = new Mat();
                    Imgproc.calcHist(Arrays.asList(hsImg), channels, new Mat(), hsHistResult, histSize, ranges, false);
                    minMaxLocResult = Core.minMaxLoc(hsHistResult);
                    maxVal = minMaxLocResult.maxVal;
                    //结果绘制时每个值的长宽为10
                    scale = 10;
                    Mat hsHistResultImg = Mat.zeros(saturationBinNum * scale, hueBinNum * scale, CvType.CV_8UC3);
                    for (int hue = 0; hue < hueBinNum; ++hue) {
                        for (int saturation = 0; saturation < saturationBinNum; ++saturation) {
                            double[] values = hsHistResult.get(hue, saturation);
                            double value = values[0];
                            int intensity = (int) Math.round(value * 255 / maxVal);
                            MatOfPoint rectangle = new MatOfPoint(
                                    new Point(hue * scale, saturation * scale),
                                    new Point((hue + 1) * scale - 1, saturation * scale),
                                    new Point((hue + 1) * scale - 1, (saturation + 1) * scale - 1),
                                    new Point((hue) * scale, (saturation + 1) * scale - 1)
                            );
                            Imgproc.fillPoly(hsHistResultImg, Arrays.asList(rectangle), Scalar.all(intensity));
                        }
                    }
                    Platform.runLater(() -> showImage(new Mat(hsHistResultImg, new Rect(0, 0, hsHistResultImg.width(), hsHistResultImg.height())), hsHist, "H-S直方图"));
                }

                //------计算一维直方图
                Mat simpleHistImg = new Mat();
                Core.copyTo(srcImg, simpleHistImg, new Mat());
                //一维直方图，横坐标分256块
                histSize = new MatOfInt(256);
                //定义亮度变化范围为0~255
                ranges = new MatOfFloat(0, 255);
                //指定calHist将计算第0通道
                channels = new MatOfInt(0);
                //H-S直方图计算结果
                Mat simpleHistResult = new Mat();
                Imgproc.calcHist(Arrays.asList(simpleHistImg), channels, new Mat(), simpleHistResult, histSize, ranges, false);
                minMaxLocResult = Core.minMaxLoc(simpleHistResult);
                maxVal = minMaxLocResult.maxVal;
                //结果绘制时每个值的长宽为1
                scale = 1;
                Mat simpleHistResultImg = Mat.zeros(256, 256 * scale, CvType.CV_8UC3);
                for (int i = 0; i < 256; ++i) {
                    double[] values = simpleHistResult.get(i, 0);
                    double value = values[0];
                    int intensity = (int) Math.round(value * 255 / maxVal);
                    MatOfPoint rectangle = new MatOfPoint(
                            new Point(i, 256 - intensity * scale),
                            new Point(i + 1, 256 - intensity * scale),
                            new Point(i + 1, 256),
                            new Point(i, 256)
                    );
                    Imgproc.fillPoly(simpleHistResultImg, Arrays.asList(rectangle), Scalar.all(255));
                }
                Platform.runLater(() -> showImage(new Mat(simpleHistResultImg, new Rect(0, 0, simpleHistResultImg.width(), simpleHistResultImg.height())), simpleHist, "一维直方图"));

                //-----计算反向投影
                if (!simpleHistResult.empty()) {
                    channels = new MatOfInt(0);
                    Mat backproj = new Mat();
                    ranges = new MatOfFloat(0, 180);
                    Imgproc.calcBackProject(Arrays.asList(hsImg), channels, simpleHistResult, backproj, ranges, 1);
                    Platform.runLater(() -> showImage(new Mat(backproj, new Rect(0, 0, backproj.width(), backproj.height())), backProjHist, "反向投影结果"));
                }

                return null;
            }
        };
        new Thread(task).start();
    }

    private void showImage(Mat img, ImageView view, String winTitle) {
        //缩放、转码
        ImageUtils.fitImgSize(img, SHOW_IMG_WIDTH, SHOW_IMG_HEIGHT);
        MatOfByte buffer = new MatOfByte();
        Imgcodecs.imencode(".jpeg", img, buffer);
        view.setImage(new Image(new ByteArrayInputStream(buffer.toArray())));
        //贴到组件上
        VBox vBox = new VBox();
        vBox.getChildren().addAll(view);
        Scene scene = new Scene(vBox, SHOW_IMG_WIDTH, SHOW_IMG_HEIGHT);
        Stage newWindow = new Stage();
//        newWindow.initModality(Modality.APPLICATION_MODAL); // 设置此模式，则窗口打开时，其他窗口无法操作
        newWindow.initModality(Modality.NONE);
        newWindow.setTitle(winTitle);
        newWindow.setScene(scene);
        newWindow.show();
    }

    @Override
    public void start(Stage stage) throws Exception {
        Button imgChooserBtn = new Button("选择图片");
        imgChooserBtn.setOnMouseClicked(e -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("选择图片");
            fileChooser.setInitialDirectory(new File(System.getProperty("user.home") + "/Downloads")
            );
            fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("All Images", "*.*"));
            File imgFile = fileChooser.showOpenDialog(stage);
            if (null == imgFile) return;
            srcImg = Imgcodecs.imread(imgFile.getAbsolutePath(), Imgcodecs.IMREAD_UNCHANGED);
            if (srcImg.empty()) System.out.println("图片读取失败");
            imageProcess();
        });
        //关闭按钮
        Button exitBtn = new Button("关闭");
        exitBtn.setOnMouseClicked(e -> Platform.exit());
        VBox rows = new VBox(0);
        rows.setPadding(new Insets(10, 10, 0, 10));
        HBox row0 = new HBox(10);
        row0.setPadding(new Insets(5, 0, 5, 0));
        row0.getChildren().addAll(imgChooserBtn, exitBtn);
        rows.getChildren().addAll(row0);
        srcImage = new ImageView();
        hsHist = new ImageView();
        simpleHist = new ImageView();
        backProjHist = new ImageView();
        HBox imgRow = new HBox(10);
        imgRow.setPadding(new Insets(10, 0, 10, 0));
        imgRow.getChildren().addAll(srcImage);
        BorderPane root = new BorderPane();
        root.setCenter(imgRow);
        root.setBottom(rows);
        Scene scene = new Scene(root, DEFAULT_WIDTH, DEFAULT_HEIGHT + 50);
        stage.setScene(scene);
        stage.setTitle("直方图的计算与绘制演示");
        stage.setResizable(false);
        stage.show();
    }
}
